﻿package com.janisRadins.asDraw {
	import com.janisRadins.asDraw.vo.CurvePosition;
	import flash.display.GraphicsPath;
	import flash.display.Sprite;
	import flash.geom.Point;
	import mx.utils.ObjectUtil;
	/**
	 * @author Jānis Radiņš
	 * Class which represents collection of curves which form continuous path
	 */
	public class Path implements ICurve, ICurveCollection {
		public static function joinGraphicPaths( ... paths):GraphicsPath {
			if (paths.length == 0) {
				return null;
			}
			var combined:GraphicsPath = new GraphicsPath();
			combined.commands = new Vector.<int>();
			combined.data = new Vector.<Number>();
			var path:GraphicsPath;
			for (var i:uint = 0; i < paths.length; i++) {
				path = paths[i] as GraphicsPath;
				combined.commands = combined.commands.concat(path.commands);
				combined.data = combined.data.concat(path.data);
			}
			return combined;
		}
		public static function moveGraphicPath(path:GraphicsPath, add:Point):GraphicsPath {
			for (var i:uint = 0; i < path.data.length; i++) {
				if (i % 2 == 0) {
					path.data[i] += add.x;
				} else {
					path.data[i] += add.y;
				}
			}
			return path;
		}
		/**
		 * Inner variable which contains list of all curves in current collection
		 */
		protected var curveList:Vector.<Curve> = null;
		/**
		 * @private
		 */
		protected var _length:Number = NaN;
		/**
		 * Constructor
		 * @param	curves Vector with curves to be used in this Path
		 */
		public function Path(curves:Vector.<Curve> = null) {
			curveList = (curves != null ? curves : new Vector.<Curve>());
		}
		/**
		 * @copy com.janisRadins.asDraw#getPointOnCurve()
		 */
		public function getPointOnCurve(position:Number):Point {
			var curvePos:CurvePosition = getCurveOnPosition(position);
			if (curvePos == null) {
				throw new Error("Curve for requested position [" + position + "] not found");
			}
			return curvePos.coordinates.add(curvePos.curve.getPointOnCurve(position - curvePos.startLength));
		}
		/**
		 * @copy com.janisRadins.asDraw#getCurveSegment()
		 */
		public function getCurveSegment(start:Number = NaN, end:Number = NaN):GraphicsPath {
			if (isNaN(start)) {
				start = 0;
			}
			if (isNaN(end)) {
				end = length;
			}
			if (start == end) {
				return new GraphicsPath();
			}
			if (start < end) {
				return getSubSegment(start, end);
			} else {
				return joinGraphicPaths(getSubSegment(start, length), getSubSegment(0, end));
			}
			return null;
		}
		/**
		 * @copy com.janisRadins.asDraw#length
		 */
		public function get length():Number {
			if (isNaN(_length) == true) {
				updateLength();
			}
			return _length;
		}
		/**
		 * Method that update total CurveCollection length value. This procedure is ran only as 
		 * CurveCollection curve list is changed or updated and only in case if length property is requested.
		 */
		private function updateLength():void {
			_length = 0;
			for each(var curve:Curve in curveList) {
				_length += curve.length;
			}
		}
		/**
		 * Method that returns Curve instance which belongs to specific position value of CurveCollection.
		 * @param	position curve length value for which corresponding curve should be found
		 * @return	Curve instance which is holding requested point
		 */
		private function getCurveOnPosition(position:Number):CurvePosition {
			var startPoint:Point = new Point();
			var startLength:Number = 0;
			var curve:Curve;
			for (var i:uint = 0; i < curveList.length; i++) {
				curve = curveList[i];
				if (position >= startLength && position <= startLength + curve.length) {
					return new CurvePosition(startPoint, startLength, curve, i);
				}
				startPoint.x += curve.anchorX;
				startPoint.y += curve.anchorY;
				startLength += curve.length;
			}
			return null;
		}
		
		protected function getSubSegment(start:Number, end:Number):GraphicsPath {
			var startCurve:CurvePosition = getCurveOnPosition(start);
			var startPoint:Point = new Point(startCurve.coordinates.x, startCurve.coordinates.y);
			var pathData:GraphicsPath = new GraphicsPath();
			pathData.moveTo(startPoint.x, startPoint.y);
			
			var segLength:Number = end - start;
			var curve:Curve;
			var s:Number = Math.max(start - startCurve.startLength, 0);
			var e:Number;
			var subPath:GraphicsPath;
			for (var i:uint = startCurve.index; i < curveList.length; i++) {
				curve = curveList[i] as Curve;
				e = Math.min(segLength + s, curve.length);
				subPath = curve.getCurveSegment(s, e);
				moveGraphicPath(subPath, startPoint);
				pathData = joinGraphicPaths(pathData, subPath);
				segLength -= e-s;
				if (segLength == 0)	break;
				if (s > 0)			s = 0;
				startPoint = startPoint.add(new Point(curve.anchorX, curve.anchorY));
			}
			return pathData;
		}
		
		protected var _curveIndex:uint = 0;
		public function get curveCount():uint {
			return curveList.length;
		}
		public function getCurveAt(index:uint):ICurve {
			if (index >= curveList.length) {
				throw new Error("Curve index out of range");
			}
			return curveList[index];
		}
		public function hasNext():Boolean {
			return _curveIndex < curveList.length - 1;
		}
		public function getNext():ICurve {
			if (hasNext() == false) {
				return null;
			}
			return curveList[_curveIndex++];
		}
		public function hasPrev():Boolean {
			return _curveIndex > 0;
		}
		public function getPrev():ICurve {
			if (hasNext() == false) {
				return null;
			}
			return curveList[_curveIndex--];
		}
		public function resetToStart():void {
			_curveIndex = 0;
		}
		public function resetToEnd():void {
			if (curveList.length > 0) {
				resetToStart();
			} else {
				_curveIndex = curveList.length-1;
			}
		}
	}
}